import asyncio, threading

class Subscribe: 
    def __init__(self, ob, f, name=None): #name可以为订阅设置名称，通过name可以删除订阅
        self.ob = ob
        self.func = f
        self.name = name

    def remove(self):
        self.ob.removeSubscribe_obj(self)

class ObservableBase:
    def __init__(self):
        self.changeActionsList = []
        self.changeActionsList_once = []

    def useFunc(self, f, params):
        if asyncio.iscoroutinefunction(f):
            asyncio.create_task(f(*params))
        else:
            f(*params)

    def trigger(self):
        [self.useFunc(v.func, [self._value]) for v in self.changeActionsList]
        [self.useFunc(v.func, [self._value]) for v in self.changeActionsList_once]
        self.changeActionsList_once = []

    def once(self, f):
        s = Subscribe(self, f)
        self.changeActionsList_once.append(s)

    def subscribe(self, f, name=None, init=False): #name可以为订阅设置名称，通过name可以删除订阅，init代表是否绑定订阅的同时先调用一次
        s = Subscribe(self, f, name)
        self.changeActionsList.append(s)
        if init:
            self.useFunc(f, [self._value])
        return s

    def removeSubscribe(self, name):
        for v in self.changeActionsList:
            if v.name == name:
                self.changeActionsList.remove(v)

    def removeSubscribe_obj(self, s):
        if s in self.changeActionsList:
            self.changeActionsList.remove(s)
        if s in self.changeActionsList_once:
            self.changeActionsList_once.remove(s)

class Observable(ObservableBase):
    def __init__(self, v, format=None):
        super().__init__()
        self.format = format
        self._value = v

    def __str__(self):
        if self.format:
            return self.format % self._value
        return self._value

    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, v):
        if v == self._value:
            return
        self._value = v
        # [v['func'](self._value) for v in self.changeActionsList]
        self.trigger()

    def setValue(self, v):
        self.value = v

    def append(self, v):
        arr = [*self._value]
        arr.append(v)
        self.setValue(arr)

    def remove(self, v):
        arr = [*self._value]
        arr.remove(v)
        self.setValue(arr)

    def pop(self, index=-1):
        arr = [*self._value]
        arr.pop(index)
        self.setValue(arr)

class Computed(ObservableBase):
    def __init__(self, obs, func, destroyer=None):
        super().__init__()
        self.obs = obs
        self.func = func
        self._value = func(self.allValue)
        self.binds = list(
            map(lambda ob: ob.subscribe(lambda v: self.callFunc()), obs)
        )
        
        if destroyer: # destroyer为一个Observable
            if not isinstance(destroyer, Observable):
                raise TypeError("destroyer must be Observable")
            destroyer.once(self.destroy)

    def destroy(self, v=True): 
        # 销毁该computed与所有ob的绑定
        # 对于临时性使用的computed，在其绑定的Observable销毁时，应该调用destroy方法，销毁该computed与所有ob的绑定
        if v:
            for s in self.binds:
                s.remove()

    def callFunc(self):
        self.value = self.func(self.allValue)

    @property
    def allValue(self):
        return [ob.value for ob in self.obs]

    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, v):
        if v == self._value:
            return
        self._value = v
        # [v['func'](self._value) for v in self.changeActionsList]
        self.trigger()

    # def subscribe(self, f, name=None, init=False): #name可以为订阅设置名称，通过name可以删除订阅，init代表是否绑定订阅的同时先调用一次
    #     self.changeActionsList.append({'func': f, 'name': name})
    #     if init:
    #         f(self._value)

    # def removeSubscribe(self, name):
    #     remove_e = []
    #     for i, v in enumerate(self.changeActionsList):
    #         if v['name'] == name:
    #             remove_e.append(v)
    #     for i in remove_e:
    #         self.changeActionsList.remove(i)

class BoolFormat:
    def __init__(self, arr, format):
        self.format = format
        self.arr = arr

    def __mod__(self, v):
        return self.format % (self.arr[0] if v else self.arr[1])

class DebounceAsync:
    def __init__(self, interval):
        self.interval = interval
        self.debounced = None

    async def timer(self, func, *args, **kwargs):
        await asyncio.sleep(self.interval)
        self.debounced = None
        if asyncio.iscoroutinefunction(func):
            await func(*args, **kwargs)
        else:
            func(*args, **kwargs)

    def __call__(self, func):
        if asyncio.iscoroutinefunction(func):
            async def decorator(*args, **kwargs):
                if self.debounced is not None:
                    self.debounced.cancel()
                self.debounced = asyncio.get_event_loop().create_task(self.timer(func, *args, **kwargs))
        else:
            def decorator(*args, **kwargs):
                if self.debounced is not None:
                    self.debounced.cancel()
                self.debounced = asyncio.get_event_loop().create_task(self.timer(func, *args, **kwargs))

        return decorator

class DebounceThread:
    def __init__(self, interval):
        self.interval = interval
        self.debounced = None

    def __call__(self, func):
        def decorator(*args, **kwargs):
            if self.debounced is not None:
                self.debounced.cancel()
            self.debounced = threading.Timer(self.interval, func, args, kwargs)
            self.debounced.start()

        return decorator


if __name__ == "__main__":
    # import time, threading
    # def testDebounce(v):
    #     print(v)

    # @DebounceAsync(3)
    # def changeB(v):
    #     b.value = v

    # a = Observable(0)
    # b = Observable(a.value)
    # a.subscribe(changeB)
    # b.subscribe(testDebounce)

    # async def test():
    #     a.value = 1
    #     await asyncio.sleep(2)

    #     a.value = 3
    #     await asyncio.sleep(5)

    # loop = asyncio.get_event_loop()
    # loop.run_until_complete(test())

    # @DebounceAsync(0.1)
    def func(vs):
        print(vs)

    async def test():
        a = Observable(1)
        b = Observable(2)
        c = Computed([a, b], func)
        a.value = 3
        b.value = 4
        await asyncio.sleep(0.2)

    asyncio.run(test())